//
//  NSObject+KVO.m
//  iOSOCTest
//
//  Created by linyr on 2017/2/9.
//  Copyright © 2017年 DQHome. All rights reserved.
//

#import "NSObject+KVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import "NSObject+DLIntrospection.h"

NSString *const kPGKVOAssociatedObservers = @"PGKVOAssociatedObservers";

///--------------------------------------
#pragma mark - PGObservationInfo
///--------------------------------------

@interface PGObservationInfo : NSObject

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) PGObservingBlock block;

@end

@implementation PGObservationInfo

- (instancetype)initWithObserver:(NSObject *)observer Key:(NSString *)key block:(PGObservingBlock)block
{
    self = [super init];
    if (self) {
        _observer = observer;
        _key = key;
        _block = block;
    }
    return self;
}

@end

///--------------------------------------
#pragma mark - Debug Help Methods
///--------------------------------------

//static NSArray *ClassMethodNames(Class c)
//{
//    NSMutableArray *array = [NSMutableArray array];
//
//    unsigned int methodCount = 0;
//    Method *methodList = class_copyMethodList(c, &methodCount);
//    unsigned int i;
//    for(i = 0; i < methodCount; i++) {
//        [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
//    }
//    free(methodList);
//
//    return array;
//}
//
//
//static void PrintDescription(NSString *name, id obj)
//{
//    NSString *str = [NSString stringWithFormat:
//                     @"%@: %@\n\tNSObject class %s\n\tRuntime class %s\n\timplements methods <%@>\n\n",
//                     name,
//                     obj,
//                     class_getName([obj class]),
//                     class_getName(object_getClass(obj)),
//                     [ClassMethodNames(object_getClass(obj)) componentsJoinedByString:@", "]];
//    printf("%s\n", [str UTF8String]);
//}
//
//

///--------------------------------------
#pragma mark - Helpers
///--------------------------------------
/**
 根据setter方法名得到getter方法名

 @param setter setter方法名
 @return getter方法名
 */
static NSString * getterForSetter(NSString *setter)
{
    if (setter.length <=0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
        return nil;
    }
    // remove 'set' at the begining and ':' at the end
    NSRange range = NSMakeRange(3, setter.length - 4);
    NSString *key = [setter substringWithRange:range];
    // lower case the first letter
    NSString *firstLetter = [[key substringToIndex:1] lowercaseString];
    key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1)
                                       withString:firstLetter];
    return key;
}

/**
 根据getter方法名得到对应的setter方法名

 @param getter getter方法名
 @return setter方法名
 */
static NSString * setterForGetter(NSString *getter)
{
    if (getter.length <= 0) {
        return nil;
    }
    // upper case the first letter
    NSString *firstLetter = [[getter substringToIndex:1] uppercaseString];
    NSString *remainingLetters = [getter substringFromIndex:1];
    // add 'set' at the begining and ':' at the end
    NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstLetter, remainingLetters];
    return setter;
}

///--------------------------------------
#pragma mark - 重写kvo子类的setter和class
///--------------------------------------


/**
 A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd
 新的setter方法的实现

 @param self <#self description#>
 @param _cmd <#_cmd description#>
 @param newValue <#newValue description#>
 */
static void kvo_setter(id self, SEL _cmd, id newValue)
{
    // getter方法名和setter方法名
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = getterForSetter(setterName);
    if (!getterName) {
        NSString *reason = [NSString stringWithFormat:@"Object %@ does not have setter %@", self, setterName];
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:reason
                                     userInfo:nil];
        return;
    }
    
    id oldValue = [self valueForKey:getterName];
    struct objc_super superclazz = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    // cast our pointer so the compiler won't complain
    // 在 Xcode 6 里，新的 LLVM 会对 objc_msgSendSuper 以及 objc_msgSend 做严格的类型检查，如果不做类型转换。Xcode 会抱怨有 too many arguments 的错误。
    void (*objc_msgSendSuperCasted)(void *, SEL, id) = (void *)objc_msgSendSuper;
    // call super's setter, which is original class's setter method
    // 调用父类的setter方法
    objc_msgSendSuperCasted(&superclazz, _cmd, newValue);
    // look up observers and call the blocks
    // 通知观察者
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));// 观察者数组
    for (PGObservationInfo *each in observers) {
        if ([each.key isEqualToString:getterName]) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                // kvo block
                each.block(self, getterName, oldValue, newValue);
            });
        }
    }
}

/**
 返回一个实例对象的父类

 @param self 实例对象
 @param _cmd useless
 @return 返回实例对象的父类
 */
static Class kvo_class(id self, SEL _cmd)
{
    return class_getSuperclass(object_getClass(self));
}

///--------------------------------------
#pragma mark - categrory KVO
///--------------------------------------

NSString *const kPGKVOClassPrefix = @"PGKVOClassPrefix_";

@implementation NSObject (KVO)

- (void)PG_addObserver:(NSObject *)observer
                forKey:(NSString *)key
             withBlock:(PGObservingBlock)block
{
// SETP 1: 检查对象的类有没有相应的 setter 方法。如果没有抛出异常；
    SEL setterSelector = NSSelectorFromString(setterForGetter(key));
    Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    if (!setterMethod) {
        NSString *reason = [NSString stringWithFormat:@"Object %@ does not have a setter for key %@", self, key];
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:reason
                                     userInfo:nil];
        return;
    }
// STEP 2: 检查对象 isa 指向的类是不是一个 KVO 类。如果不是，新建一个继承原来类的子类，并把 isa 指向这个新建的子类；
    Class clazz = object_getClass(self);
    NSString *clazzName = NSStringFromClass(clazz);
    if (![clazzName hasPrefix:kPGKVOClassPrefix]) {
        clazz = [self makeKvoClassWithOriginalClassName:clazzName];
        // 修改 isa 指针
        object_setClass(self, clazz);
    }
// STEP 3: 检查对象的 KVO 类重写过没有这个 setter 方法。如果没有，添加重写的 setter 方法；
    if (![self hasSelector:setterSelector]) {
        const char *types = method_getTypeEncoding(setterMethod);
        class_addMethod(clazz, setterSelector, (IMP)kvo_setter, types);
    }

// STEP 4: 添加这个观察者
    PGObservationInfo *info = [[PGObservationInfo alloc] initWithObserver:observer Key:key block:block];
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
    if (!observers) {
        observers = [NSMutableArray array];
        // kvo class 关联kvo相关信息的数组
        objc_setAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [observers addObject:info];
}


/**
 runtime创建kvo子类

 @param originalClazzName 原始类名
 @return kvo类名
 */
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName
{
    // kvo class name
    NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClazzName];
    Class clazz = NSClassFromString(kvoClazzName);
    if (clazz) {
        return clazz;
    }
    // 创建 kvo class
    Class originalClazz = object_getClass(self);
    // 动态创建新的类，传一个父类，类名，额外空间通常为0
    Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
    // 只重写了 class 方法，跟 Apple 一样，这时候我们也企图隐藏这个子类的存在。
    const char *types = method_getTypeEncoding(class_getInstanceMethod(originalClazz, @selector(class)));
    class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
    // 告诉runtime这个类的存在
    objc_registerClassPair(kvoClazz);
    
    return kvoClazz;
}

/**
 判断指定类是否有指定的方法

 @param selector 方法名
 @return 是否有指定的方法
 */
- (BOOL)hasSelector:(SEL)selector
{
    unsigned int methodCount = 0;
    // 返回指定类的方法Method数组，方法前缀`class`，第一个参数Class类型
    Method* methodList = class_copyMethodList(object_getClass(self), &methodCount);
    for (unsigned int i = 0; i < methodCount; i++) {
        // 通过Method得到SEL，方法前缀`method`，第一个参数Method类型
        SEL thisSelector = method_getName(methodList[i]);
        if (thisSelector == selector) {
            // 释放C数组
            free(methodList);
            return YES;
        }
    }
    free(methodList);
    return NO;
}

- (void)PG_removeObserver:(NSObject *)observer forKey:(NSString *)key
{
    // 移除kvo信息数组中的观察者和key
    NSMutableArray* observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
    PGObservationInfo *infoToRemove;
    for (PGObservationInfo* info in observers) {
        if (info.observer == observer && [info.key isEqual:key]) {
            infoToRemove = info;
            break;
        }
    }
    [observers removeObject:infoToRemove];
    
    // TODO:如果key没有其他观察者，要删除kvo-setter方法
    // TODO:如果没有任何key被观察，要删除kvo-subClass
}

@end


